package simulator;

public abstract class BufferCache {
	
	protected Frame[] frames;
	protected int hits, misses;
	
	//public static BufferCache instance;
	
	public FileSystem fs;
	public SystemSim ss;
	public JournalArea ja;
	
	public void setsystem(FileSystem fs, SystemSim ss, JournalArea ja)
	{
		this.fs = fs;
		this.ss = ss;
		this.ja = ja;
	}
	
	public BufferCache(int numFrames)
	{
		frames = new Frame[numFrames];
		
		for(int i = 0; i < frames.length; i++)
			frames[i] = new Frame();
		
		hits = misses = 0;
	}
	
	protected int findBlock(String fileName)
	{
		for(int i = 0; i < frames.length; i++)
		{
			Frame currentFrame = frames[i];
			if(frames[i].block != null && currentFrame.block.getName().equals(fileName) && currentFrame.isUptodate){ //buffer cache hit!
				return i;
			}
		}
		return -1;
	}
	
	public abstract void updateBlock(String fileName, int data);{}
	
	
	
	// Lazy commit form BufferCache to JournalArea
	public void lazyCommit()
	{
		if(frames.length >= 50/*BufferCache is almost full, time for commit, 50 is changable*/)
		{
			commitAll();
		}
	}
	
	// Async periodically commit, called in EntryPoint.
	public abstract void commitAll();{}
	
	public abstract void checkpointAll();{}
	
	public abstract void evictDirty(Block b);{}
	
	/**
	 * read a block of data.
	 * If desired block is not in the cache, read it into cache then read the block.
	 * @param fileName
	 * @return the desired data
	 */
	public int readBlock(String fileName)
	{
		//lazyCommit();
		int frameNo = findBlock(fileName);
		
		if(frameNo >= 0){
			hits++;
			frames[frameNo].timeStamp = ss.getTime();
			return frames[frameNo].getData();
			
		}
		
		misses++;
		
		frameNo = readIn(fileName);
		Frame fr = frames[frameNo];
		fr.isUptodate = true; //new block is up-to-date, since just got it from file system!
		frames[frameNo].timeStamp = ss.getTime();
		return frames[frameNo].getData();
	}
	
	/**
	 * Find a frame to store a block. This block will be read in from the file system.
	 * We do not need to update the frame's timestamp here, because this function will always
	 * be followed immediately by a read/write to that frame, and that is when the timestamp gets updated
	 * @param fileName = the name of the file to bread in to the buffer cache
	 * Returns: the index of the frame that stores the block of data
	 */
	protected int readIn(String fileName)
	{
		int lru = LRU();//index of frame to use for reading in block
		
		Frame f = frames[lru];
		if(f.block != null){ //frame f has a block currently
			evict(f);} //f now has no block
		
		try{
		FileObject file = fs.getFile(fileName);

		Block b= new Block(fileName, file.getData());
		f.block = b;
		f.isUptodate = true;

		}
		catch(NullPointerException e) {
			System.out.println("\nFile Does not exist!");
		}
		
		return lru;
	}
	
	
	/**
	 * Choose a frame for reading in a new block, based on LRU policy if all frames are full
	 * @return
	 */
	protected int LRU()
	{
		int frameNo = emptyFrame();
		if(frameNo >= 0)
			return frameNo; //there is an empty frame, so return it
		
		frameNo = getOldestFrame(); //start search at first frame
		return frameNo;
	}
	
	
	
	protected int getOldestFrame()
	{
		int frameNo = 0;
		for(int i = 1; i < frames.length; i++)
		{//compare earliest timestamped frame against every other frame
			Frame currentFrame = frames[i];
			// Evit the unfrozen or forzen && out of date one.
			//if((currentFrame.timeStamp < frames[frameNo].timeStamp && !currentFrame.isFrozen)||(currentFrame.isFrozen && !currentFrame.isUptodate)) 
			if(currentFrame.timeStamp < frames[frameNo].timeStamp && currentFrame.isFrozen==false)
			{//we have a new earliest frame
				frameNo = i;}
		}
	
		return frameNo;
	}
	
	/**
	 * Return the index of a frame that currently has no block stored in it
	 * @return
	 */
	protected int emptyFrame()
	{
		for(int i = 0; i < frames.length; i++)
		{
			Frame f = frames[i];
			if(f.block == null)
				return i; //frame 'i' has no block in it
		}
		
		return -1; //there are no empty frames
	}

	/**
	 * Remove the block data from frame f
	 * @param f
	 */
	protected void evict(Frame f)
	{
		Block b = f.block;
		if(b != null){
			
			if(f.isDirty)
				evictDirty(b);
			f.block = null; //free the frame so another block can be put in it
		}
		
		f.resetFlags();
	}
	
	public String toString()
	{
		String str = "\n";
		for(int i = 0; i < frames.length; i++)
		{
			Frame f = frames[i];
			Block b = f.block;
			if(f.block != null){
				str += "Block name: " + b.getName()
						+ "\tBlock Data: " + b.getData() + "\n";
			}
		}
		
		return str;
	}
	
	public int getMisses(){
		return misses;}
	
	public int getHits(){
		return hits;}
	
	public Frame[] getFrames()
	{
		return frames;
	}
	
	public abstract void flushDirty();{}
	
	public abstract String SimString(); {}

}
